/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.context;

import org.slf4j.MDC;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Servlet上下文
 *
 * @auther ebert_chan
 */
public class ServletContext {

    /**
     * 上下文中的其他扩展信息
     */
    public static final String EXT_PROPERTY_PREFIX = "ext-";

    /**
     * 上下文中的必须属性，需要往下传递的值
     */
    public static final String CONTEXT_PROPERTY_PREFIX = "context-";

    /**
     * 日志追踪ID
     */
    public static final String CONTEXT_TRACE_ID = CONTEXT_PROPERTY_PREFIX + "trace-id";

    /**
     * 语言设置
     */
    public static final String CONTEXT_LANGUAGE = CONTEXT_PROPERTY_PREFIX + "language";

    /**
     * 日志信息
     */
    public static final String EXT_LOG = EXT_PROPERTY_PREFIX + "log";

    /**
     * 上下文中存储的扩展信息
     */
    private Map<String, Object> properties = new HashMap<>();

    /**
     * 上下文中获取一个key的值
     *
     * @param key
     * @auther ebert_chan
     */
    public Object get(String key) {
        return this.properties.get(key);
    }

    /**
     * 上下文中删除一个key
     *
     * @param key
     * @auther ebert_chan
     */
    public void remove(String key) {
        this.properties.remove(key);
    }

    /**
     * @return
     * @auther ebert_chan
     * @see java.util.HashMap#entrySet()
     */
    public Set<Map.Entry<String, Object>> entrySet() {
        return this.properties.entrySet();
    }

    /**
     * 克隆
     */
    public ServletContext clone() {
        ServletContext copy = new ServletContext();
        copy.properties = new HashMap<>(this.properties);
        return copy;
    }

    public static final class ServletContextHolder {

        private ServletContextHolder() {
        }

        /**
         * threadLocal
         */
        private static final ThreadLocal<ServletContext> threadLocal = new ThreadLocal<>();

        /**
         * 上下文中加入一个key-value类型的值，改值为扩展类型，不会跨线程传递
         *
         * @param key
         * @param value
         * @auther ebert_chan
         */
        public static void setExtProperty(String key, Object value) {
            Assert.isTrue(key.startsWith(EXT_PROPERTY_PREFIX), "key必须以\"" + EXT_PROPERTY_PREFIX + "\"开头");
            ServletContext context = getServletContext();
            if (context == null) {
                context = new ServletContext();
                threadLocal.set(context);
            }
            context.properties.put(key, value);
        }

        /**
         * 上下文中加入一个key-value类型的值，设置后该值会跨线程传递
         *
         * @param key
         * @param value
         * @auther ebert_chan
         */
        public static void setContextProperty(String key, String value) {
            Assert.isTrue(key.startsWith(CONTEXT_PROPERTY_PREFIX), "key必须以\"" + CONTEXT_PROPERTY_PREFIX + "\"开头");

            ServletContext context = getServletContext();
            if (context == null) {
                context = new ServletContext();
                threadLocal.set(context);
            }
            context.properties.put(key, value);

            if (CONTEXT_TRACE_ID.equals(key)) {
                // 日志ID传递
                MDC.put(CONTEXT_TRACE_ID, value);
            }
        }

        /**
         * 清理当前上下文中的信息
         *
         * @auther ebert_chan
         */
        public static void clear() {
            ServletContext context = getServletContext();
            if (context != null) {
                context.properties.clear();
            }
            threadLocal.remove();
            // 移除traceId
            MDC.clear();
        }

        /**
         * 获取当前线程Servlet上下文
         *
         * @return
         * @auther ebert_chan
         */
        public static ServletContext getServletContext() {
            return threadLocal.get();
        }

        /**
         * 从当前线程Servlet上下文获取一个key的值
         *
         * @param key
         * @return
         * @auther ebert_chan
         */
        @SuppressWarnings("unchecked")
        public static <T> T getValue(String key) {
            ServletContext context = getServletContext();
            return context != null ? (T) context.get(key) : null;
        }

        /**
         * 获取上下文的复制版本
         *
         * @return
         * @auther ebert_chan
         */
        public static Map<String, Object> getCopyOfContextMap() {
            Map<String, Object> contextMap = null;
            ServletContext context = getServletContext();
            if (context != null && !context.properties.isEmpty()) {
                contextMap = new HashMap<>(context.properties);
            }
            return contextMap;
        }

        /**
         * 传递Context
         *
         * @param operator
         * @auther ebert_chan
         */
        public static void dispatch(ContextOperator operator) {
            ServletContext context = getServletContext();
            if (context != null && !context.properties.isEmpty()) {
                for (Entry<String, Object> entry : context.properties.entrySet()) {
                    String key = entry.getKey();
                    if (!key.startsWith(CONTEXT_PROPERTY_PREFIX)) {
                        continue;
                    }
                    Object value = entry.getValue();
                    operator.apply(key, (String) value);
                }
            }
        }

        /**
         * 设置上下文信息
         *
         * @param contextMap
         * @auther ebert_chan
         */
        public static void setContextMap(Map<String, Object> contextMap) {
            if (contextMap == null || contextMap.isEmpty()) {
                return;
            }
            for (Entry<String, Object> entry : contextMap.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (value == null) {
                    continue;
                }
                if (key.startsWith(CONTEXT_PROPERTY_PREFIX)) {
                    setContextProperty(key, value.toString());
                }
                if (key.startsWith(EXT_PROPERTY_PREFIX)) {
                    setExtProperty(key, value.toString());
                }
            }
        }

    }

    @FunctionalInterface
    public static interface ContextOperator {

        void apply(String key, String value);

    }

}
